/*
 * Copyright (C) 2012 genzo
 */
package jp.gr.java_conf.genzo.wolrepeater;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

/**
 * 通信系サービス.
 *
 */
public class WolRepeaterService extends Service {

    /** Macアドレスバイト数. */
    private static final int MAC_COUNT = 6;
    /** Notification用ID. */
    private static final int NOTIFICATION_ID = 141421356;
    /** 通知間隔. */
    private static final int WAIT = 1000 * 60;
    /** 終了時間. */
    private static final int END_TIMER = 1000 * 60 * 60 * 2;

    /** ブロードキャストアドレス. */
    private static final InetAddress ALL;

    /** IP アドレスパターン. */
    private static final String IP_PATTERN = "^(((\\d)|([1-9]\\d)|(1\\d{1,2})|(2[0-4]\\d)|(25[0-5]))\\.){3}((\\d)|([1-9]\\d)|(1\\d{1,2})|(2[0-4]\\d)|(25[0-5]))$";

    /** ワーカースレッド. */
    private final AbsWorker[] mWorkers = new AbsWorker[2];

    /** 終了時間. */
    private long mEndTime = -1;

    static {
        InetAddress addr = null;
        try {
            addr = InetAddress.getByName("255.255.255.255");
        } catch (UnknownHostException e) {
            ;// 例外でないはず
        }
        ALL = addr;
    }

    /*
     * (非 Javadoc)
     *
     * @see android.app.Service#onBind(android.content.Intent)
     */
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    /**
     * ワーカースレッドベースクラス.
     */
    private abstract class AbsWorker extends Thread {

        /** 停止フラグ. */
        private boolean mIsAbort = false;

        /**
         * 停止.
         */
        public void abort() {
            interrupt();
            mIsAbort = true;
        }

        /*
         * (非 Javadoc)
         *
         * @see java.lang.Thread#run()
         */
        @Override
        public void run() {
            setName(getJobName());
            super.run();

            try {
                do {
                    execute();
                    sleep(WAIT);
                    checkFinish();
                } while (!mIsAbort);
            } catch (InterruptedException e) {
                ;// NOP
            }

            L.d("finish !! name=" + getJobName());
        }

        /**
         * スレッド名 取得.
         *
         * @return スレッド名
         */
        public abstract String getJobName();

        /**
         * 実行.
         */
        public abstract void execute();

    }

    /*
     * (非 Javadoc)
     *
     * @see android.app.Service#onCreate()
     */
    @Override
    public void onCreate() {
        super.onCreate();

        mWorkers[0] = new WOLThread();
        mWorkers[1] = new PingThread();
        for (AbsWorker w : mWorkers) {
            w.start();
        }

    }

    /**
     * WOL Thread.
     */
    private class WOLThread extends AbsWorker {
        @Override
        public String getJobName() {
            return "WOL";
        }

        @Override
        public void execute() {
            sendMagicPacket();
        }
    }

    /**
     * Ping Thread.
     */
    private class PingThread extends AbsWorker {
        @Override
        public String getJobName() {
            return "PING";
        }

        @Override
        public void execute() {
            WolRepeaterApp.getApp().sendPing();
        }
    }

    /**
     * 実行中判定.
     */
    private synchronized void checkFinish() {
        if (System.currentTimeMillis() > mEndTime) {
            // 終わり
            for (int idx = 0; idx < mWorkers.length; idx++) {
                AbsWorker w = mWorkers[idx];
                if (w == null) {
                    continue;
                }

                mWorkers[idx] = null;
                w.abort();
                try {
                    w.join();
                } catch (InterruptedException e) {
                    ;// 気にしない
                }

            }
        }
    }

    /*
     * (非 Javadoc)
     *
     * @see android.app.Service#onStartCommand(android.content.Intent, int, int)
     */
    @SuppressWarnings("deprecation")
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        mEndTime = System.currentTimeMillis() + END_TIMER;

        int icon = R.drawable.ic_stat_running;
        String title = getString(R.string.notify_title);
        long when = System.currentTimeMillis();

        Notification notification = new Notification(icon, title, when);

        String detail = getString(R.string.notify_message, new Date(mEndTime));

        Intent notify = new Intent(this, WolRepeaterActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this, 0, notify, 0);
        notification.setLatestEventInfo(this, title, detail, pi);

        startForeground(NOTIFICATION_ID, notification);

        return super.onStartCommand(notify, flags, startId);
    }

    /*
     * (非 Javadoc)
     *
     * @see android.app.Service#onDestroy()
     */
    @Override
    public void onDestroy() {
        mEndTime = -1;
        checkFinish();
        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        manager.cancelAll();
        super.onDestroy();
    }

    /**
     * MagicPaket 送信.
     */
    private void sendMagicPacket() {
        L.d("sendMagicPacket");
        for (MachineInfo item : WolRepeaterApp.getApp().getModel()) {
            String mac = item.getMac();
            L.d("wol target=" + mac);
            sendPacket(item);
        }
    }

    /**
     * WOL Send.
     *
     * @param item
     *            送信先情報
     *
     * @return true:成功 false:失敗
     */
    private boolean sendPacket(MachineInfo item) {
        String mac = item.getMac();
        int port = item.getPort();

        byte[] macBytes = getMacBytes(mac);
        byte[] bytes = new byte[6 + 16 * macBytes.length];
        for (int i = 0; i < 6; i++) {
            bytes[i] = (byte) 0xff;
        }
        for (int i = 6; i < bytes.length; i += macBytes.length) {
            System.arraycopy(macBytes, 0, bytes, i, macBytes.length);
        }

        boolean result = true;
        DatagramSocket socket = null;
        try {
            socket = new DatagramSocket();
            DatagramPacket packet = new DatagramPacket(bytes, bytes.length,
                    ALL, port);
            socket.send(packet);
        } catch (IOException e) {
            L.e("Send Error!!", e);
            result = false;
        } finally {
            if (socket != null) {
                socket.close();
            }
        }

        return result;
    }

    /**
     * MAC Address テキスト形式→byte[]変換.
     *
     * @param macStr
     *            Macアドレス
     * @return 送信データ.
     */
    private static byte[] getMacBytes(String macStr) {
        byte[] bytes = new byte[MAC_COUNT];
        String[] hex = macStr.split("(\\:|\\-)");
        if (hex.length != MAC_COUNT) {
            throw new IllegalArgumentException("Invalid MAC address.");
        }
        try {
            for (int i = 0; i < MAC_COUNT; i++) {
                bytes[i] = (byte) Integer.parseInt(hex[i], 16);
            }
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException(
                    "Invalid hex digit in MAC address.");
        }
        return bytes;
    }

    /**
     * マックアドレスチェック.
     *
     * @param mac
     *            MAC address
     * @return true:OK false:NG
     */
    public static boolean checkMacAddr(String mac) {
        boolean result = false;

        try {
            getMacBytes(mac);
            result = true;
        } catch (Exception e) {
            ;// error
        }

        return result;

    }

    /**
     * IPアドレスチェック.
     *
     * @param ip
     *            IP address
     * @return true:OK false:NG
     */
    public static boolean checkIpAddr(String ip) {
        Pattern pattern = Pattern.compile(IP_PATTERN);
        Matcher matcher = pattern.matcher(ip);

        return matcher.matches();

    }

    /**
     * PORT No チェック.
     *
     * @param strPort
     *            port
     * @return true:OK false:NG
     */
    public static boolean checkPort(String strPort) {
        boolean result = false;

        try {
            result = Integer.parseInt(strPort) > 0;
        } catch (Exception e) {
            ;
        }

        return result;
    }

}
